%include "pm.inc"

org   0x9000

jmp   LABEL_BEGIN

; 段初始化，初始化时只能传入常量
[SECTION .gdt]
;                                  段基址     段界限               属性
LABEL_GDT:          Descriptor     0,         0,                   0  
LABEL_DESC_CODE32:  Descriptor     0,         SegCode32Len - 1,    DA_C + DA_32
LABEL_DESC_VIDEO:   Descriptor     0B8000h,   0ffffh,              DA_DRW

GdtLen     equ    $ - LABEL_GDT
GdtPtr     dw     GdtLen - 1
           dd     0

SelectorCode32    equ   LABEL_DESC_CODE32 -  LABEL_GDT
SelectorVideo     equ   LABEL_DESC_VIDEO  -  LABEL_GDT

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov     ax, cs
    mov     ds, ax
    mov     es, ax
    mov     ss, ax
    mov     sp, 0100h

    ; LABEL_SEG_CODE32是一段代码的起始地址，
    ; 以下代码功能就是将这个地址写入到byte2,byte3,byte4,byte7中
    ; 类似于完成：LABEL_DESC_CODE32: Descriptor LABEL_SEG_CODE32, SegCode32Len-1, DA_C + DA_32;
    ; 但是段初始化时只能传入常量，LABEL_SEG_CODE32的值只有编译之后才能知道，所以不能直接写入
    xor     eax, eax
    mov     ax, cs
    shl     eax, 4
    add     eax, LABEL_SEG_CODE32
    mov     word [LABEL_DESC_CODE32 + 2], ax    ; (uint16_t*)((uint8_t*)LABEL_DESC_CODE32 + 2) = ax;
    shr     eax, 16
    mov     byte [LABEL_DESC_CODE32 + 4], al
    mov     byte [LABEL_DESC_CODE32 + 7], ah

    xor     eax, eax
    mov     ax, ds
    shl     eax, 4
    add     eax, LABEL_GDT
    mov     dword [GdtPtr + 2], eax

    lgdt    [GdtPtr]

    cli     ; 关中断

    in      al, 92h
    or      al, 00000010b
    out     92h, al

    mov     eax, cr0
    or      eax, 1
    mov     cr0, eax

    jmp     dword SelectorCode32: 0

; 以下代码执行在保护模式下，功能为显示一串字符。
; 保护模式两个显著特点：
;   1. 寻址空间从实模式的 1M 增强到 4G
;   2. 不同代码有不同优先级，高优先级代码可执行特殊指令，
;       而低优先级代码中，某些重要指令就无法执行了。
; 于是进入保护模式需要解决两个问题：
;   1. 如何访问超过 1M 的内存地址
;   2. 如何设置不同代码的优先级
; 针对问题1，在实模式下，cpu是16位的，寄存器16位，数据总线16位，
; 地址总线20位(2^20=1M)，所以要想访问1M之外的地址，就需要使用32位地址总线。
; Intel通过8个字节组成的结构体[byte0~byte7]解决这一问题：用byte2,byte3,byte4,byte7
; 通过调用pm.inc中的宏就可以设置好需要访问的内存及访问的长度

; 这四个字节组成32位的地址，用byte0,byte1,以及byte6的前4位组成20位的数据，
; 表示要访问的内存的长度。pm.inc中定义了这个数组结构。

; gs是计算机的一个专用寄存器，主要用于指向显存，
; 当我们将信息写入到gs指向的内存后，信息就会显示在屏幕上。
; 用于显示字符的显存，其内存地址从0xB800h开始，每两个字节表示一个字符。
; 这两个字节中，第一个字节表示颜色；第二个字节表示字符的ASCII码。
; 屏幕一行能显示80个字符，所以(80*11)表示从第11行开始显示。
 
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
    mov     ax, SelectorVideo
    mov     gs, ax
    mov     si, msg
    mov     ebx, 10
    mov     ecx, 2          ; 表示显示一个字符需要两个字节

showChar:
    mov     edi, (80*11)    ; 每行80个字符，从第11行开始显示
    add     edi, ebx        ; ebx=10; 即从第(10+1)列开始显示字符
    mov     eax, edi
    mul     ecx
    mov     edi, eax
    mov     ah, 0ch         ; ah 存储了字符的颜色字节
    mov     al, [si]        ; al 存储了字符的ASCII码
    cmp     al, 0
    je      end
    add     ebx, 1
    add     si, 1
    mov     [gs:edi], ax
    jmp     showChar

end:
    jmp     $

msg:
    DB      "Protect Mode", 0

SegCode32Len    equ     $ - LABEL_SEG_CODE32

